import java.io.File;
import java.io.FileNotFoundException;
import java.util.ArrayList;
import java.util.Enumeration;
import java.util.Hashtable;
import java.util.Scanner;

/**
 * Created by IntelliJ IDEA.
 * User: nuwansam
 * Date: Oct 29, 2009
 * Time: 10:17:28 PM
 * To change this template use File | Settings | File Templates.
 */
public class dfa {

    Hashtable<String, DfaNode> idToNodeMap;
    private DfaNode initNode;
    private String code;
    private ArrayList<String> tokens;
    private int lastTokenEnd;
    private boolean isFreeForm=true;
    private boolean isLastTokenFreeForm=false;

    //state~digit:state;space:state
    public void getGraph() {

        String str = "", nodeID = "";
        String[] descendents;
        Scanner scan = null;
        try {
            scan = new Scanner(new File("dfa.txt"));
        } catch (FileNotFoundException e) {
            e.printStackTrace();  //To change body of catch statement use File | Settings | File Templates.
        }
        while (scan.hasNext()) {
            str = scan.nextLine();
            nodeID = str.split("~")[0];
            descendents = str.split("~")[1].split(";");
            DfaNode node = getNode(nodeID);
            if (initNode == null) initNode = node;
            for (int i = 0; i < descendents.length; i++) {
                String condition = descendents[i].split(":")[0];
                String nextNodeID = descendents[i].split(":")[1];
                if (condition.equals("%col")) condition = ":";
                node.addNode(condition, getNode(nextNodeID));
            }
        }

        scan.close();


    }

    private boolean checkCodeFile(File file) {
        Scanner scan = null;
        String str;
        try {
            scan = new Scanner(file);
        } catch (FileNotFoundException e) {
            e.printStackTrace();  //To change body of catch statement use File | Settings | File Templates.
        }
        while (scan.hasNextLine()) {
            str = scan.nextLine();
            str += " ";
            boolean b = checkCode(str);
            if (!b) return b;
        }
        return true;

    }

    private DfaNode getNode(String nodeID) {
        DfaNode node = null;
        if (idToNodeMap.get(nodeID) == null) {
            node = new DfaNode(nodeID);
            idToNodeMap.put(nodeID, node);
        }
        return idToNodeMap.get(nodeID);
    }


    public dfa() {
        idToNodeMap = new Hashtable<String, DfaNode>();
        tokens = new ArrayList<String>();
        getGraph();

    }

    public void scanDocument(String filename) {
//         dfa dfa = new dfa();
        boolean b;
        b = checkCodeFile(new File(filename));
        if (!b) {
            System.out.println("Incorrect Syntax");
        }
        ArrayList<String> strs= getTokens();
        for(String s:strs){
            System.out.println(s);
        }
        this.limit = this.tokens.size();
    }

    int index = 0;
    int limit;
    int laindex = 0; // lookahead index.
    public String nextToken() {
        laindex = index + 1;   // reset lookahead index to next token.
        if (index < limit)   {
            return this.tokens.get(index++);
        } else {
            return null;
        }
    }

    public void consume(int numTokens) {
        this.index += numTokens;
    }

    public boolean hasMoreTokens() {
        return index < limit;
    }

    public void back() {
        this.index --;
    }

    public void resetLookAhead() {
        this.laindex = index;
    }

    public String lookAhead() {
        return this.tokens.get(laindex++);
    }





    public static void main(String[] args) {
//        dfa dfa = new dfa();
//        boolean b;
//        b = dfa.checkCodeFile(new File("code.txt"));
//        if (!b) {
//            System.out.println("Incorrect Syntax");
//        }
//        ArrayList<String> strs=dfa.getTokens();
//        for(String s:strs){
//            System.out.println(s);
//        }
        dfa dfa = new dfa();
        dfa.scanDocument("code.txt");
        while (dfa.hasMoreTokens())
            System.out.println(dfa.nextToken());
    }

    private boolean checkCode(String code) {
        this.code = code;
        DfaNode currNode = initNode;

        int cpos = 0;
        lastTokenEnd = 0;
        while (cpos < code.length()) {
            cpos = checkPath(cpos, initNode, "");
            if (cpos == -1) return false;
        }
        return true;
    }

    public ArrayList<String> getTokens(){
        return tokens;
    }
    
    private void addToken(String token) {
        if (!token.trim().equals("")) {

            if(isLastTokenFreeForm && isFreeForm){
                String str=tokens.get(tokens.size()-1);
                tokens.remove(tokens.size()-1);
                str+=token.trim();
                tokens.add(str);
            }
            else tokens.add(token);

            isLastTokenFreeForm=isFreeForm;
            if (token.equals("<")) isFreeForm = false;
            if (token.equals(">")) isFreeForm = true;
        }

    }
    //returns -1 if incorrect or an integer which corresponds to the current position in the code

    private int checkPath(int cpos, DfaNode node, String prevCondition) {

        Enumeration<String> en = node.map.keys();
        String token;

        if (!en.hasMoreElements()) {
            token = code.substring(lastTokenEnd, cpos);
            if (isLookAhead(prevCondition)) {
                token = code.substring(lastTokenEnd, cpos - 1);
            }
            addToken(token);
            lastTokenEnd = cpos;
            if (isLookAhead(prevCondition)) {
                lastTokenEnd = cpos - 1;
                return cpos - 1;
            }
            return cpos;

        }

        while (en.hasMoreElements()) {
            String condition = en.nextElement();
            if (isFreeForm && cpos<code.length() && code.charAt(cpos) == '<') isFreeForm = false;
            if (isFreeForm) {
                if (code.substring(cpos, code.length()).contains("<")) {
                    String ccode=code.substring(cpos,code.length());
                    token = ccode.substring(0,ccode.indexOf("<"));
                    addToken(token);
                    lastTokenEnd = cpos + ccode.indexOf("<");
                    return cpos + ccode.indexOf("<");
                }
                token = code.substring(cpos, code.length());
                addToken(token);
                lastTokenEnd = cpos;
                return code.length();
            }
            if (conditionMatch(condition, code.charAt(cpos))) {
                return checkPath(cpos + 1, node.map.get(condition), condition);
            }
        }

        return -1;
    }

    private boolean isLookAhead(String condition) {
        if (condition.startsWith("^")) return true;
        return false;
    }

    private boolean conditionMatch(String condition, char c) {
        int cint = c;
        if (condition.startsWith("^")) { //lookahead
            condition = condition.substring(1, condition.length());
        }

        if(condition.equals("%qm")){
            if(c=='?') return true;
            return false;
        }else if (condition.startsWith("?")) {            //not operator
            if (c == condition.charAt(1)) return false;
            return true;
        } else if (condition.equals("digit")) {
            if (cint >= '0' && cint <= '9')
                return true;
            return false;
        } else if (condition.equals("letter")) {
            if ((cint >= 'a' && cint <= 'z') || (cint >= 'A' && cint <= 'Z') || cint == '-') return true;
            return false;
        } else if (condition.equals("space")) {
            if (c == ' ') return true;
            return false;
        } else if (condition.contains(",")) {
            String[] str = condition.split(",");
            for (int i = 0; i < str.length; i++) {
                if (conditionMatch(str[i], c) == true) return true;
            }
            return false;

        }
        else if (condition.length() == 1) {
            if (condition.charAt(0) == c) return true;
            return false;
        }
        return false;

    }
}
